#define ZCORE_SOURCE
#include "RecordFile.hpp"
#include "IOObject.hpp"

namespace zzz {
typedef pair<int, zint64> TableItem;
SIMPLE_IOOBJECT(TableItem);

RecordItem::RecordItem(FILE *fp, zint64 itebegin_pos_, RecordItem *parent)
  :fp_(fp), itebegin_pos__(itebegin_pos_), child_(NULL), repeat_label_(-1), parent_(parent)
{
    SetPos(itebegin_pos__+sizeof(zint64));
}

//////////////////////////////////////////////////////////////////////////
// Read
void RecordItem::ReadTable() {
  SetPos(itebegin_pos__);
  IOObj::ReadFileB(fp_, table_begin_pos_);
  SetPos(table_begin_pos_);
  IOObject<vector<TableItem> >::ReadFileB(fp_, table_);
}

RecordItem* RecordItem::ReadChild(const zint32 label) {
  ZCHECK(CheckLabelExist(label));
  if (child_) delete child_;
  child_=new RecordItem(fp_, GetChildPos(label), this);
  child_->ReadTable();
  return child_;
}

RecordItem* RecordItem::ReadFinish() {
  return parent_;
}

RecordItem* RecordItem::ReadRepeatBegin(const zint32 label)
{
  ZCHECK(CheckLabelExist(label));
  if (child_) delete child_;
  child_ = new RecordItem(fp_, GetChildPos(label), this);
  child_->ReadTable();
  child_->repeat_label_=0;
  return child_;
}

RecordItem* RecordItem::ReadRepeatChild()
{
  if (parent_->repeat_label_>=0) {
    // It's parent is repeat head.
    return parent_->ReadRepeatChild();
  } else {
    // Itself is repeat head.
    repeat_label_++;
    if (!CheckLabelExist(repeat_label_))
      return NULL;
    return ReadChild(repeat_label_);
  }
}

size_t RecordItem::GetRepeatNumber()
{
  if (parent_->repeat_label_>=0) {
    // It's child is repeat head.
    return parent_->GetItemNumber();
  } else {
    // Itself is repeat head.
    return GetItemNumber();
  }
}

RecordItem* RecordItem::ReadRepeatEnd()
{
  if (parent_->repeat_label_>=0) {
    return parent_->ReadRepeatEnd();
  } else {
    if (child_) {
      child_->ReadFinish();
      delete child_;
      child_=NULL;
    }
    return ReadFinish();
  }
}

size_t RecordItem::Read(const zint32 label, void *data, size_t size, size_t count) {
  if (!CheckLabelExist(label)) return 0;
  SetPos(GetChildPos(label));
  return Read(data, size, count);
}

size_t RecordItem::Read(void *data, size_t size, size_t count) {
  return fread(data, size, count, fp_);
}


//////////////////////////////////////////////////////////////////////////
// Write
RecordItem* RecordItem::WriteChild(const zint32 label) {
  ZCHECK_FALSE(CheckLabelExist(label)) 
    << "Label "<<label<<" already existed!\n";
  zint64 pos=GetPos();
  if (child_) delete child_;
  child_=new RecordItem(fp_, pos, this);
  table_.push_back(make_pair(label, pos));
  return child_;
}

RecordItem* RecordItem::WriteFinish() {
  // Save current position.
  table_begin_pos_ = GetPos();
  // Write table.
  IOObject<vector<TableItem> >::WriteFileB(fp_, table_);
  zint64 end_pos = GetPos();
  // Jump back.
  SetPos(itebegin_pos__);
  // Write table begin position.
  IOObject<zint64>::WriteFileB(fp_, table_begin_pos_);
  // Jump to end
  SetPos(end_pos);
  return parent_;
}

RecordItem* RecordItem::WriteRepeatBegin(const zint32 label)
{
  WriteChild(label);
  child_->repeat_label_=0;
  return child_;
}

RecordItem* RecordItem::WriteRepeatChild()
{
  if (parent_->repeat_label_>=0) {
    // It's parent is repeat head.
    return parent_->WriteRepeatChild();
  } else {
    // Itself is repeat head
    if (child_) {
      child_->WriteFinish();
    }
    repeat_label_++;
    return WriteChild(repeat_label_);
  }
}

RecordItem* RecordItem::WriteRepeatEnd()
{
  if (parent_->repeat_label_>=0) {
    return parent_->WriteRepeatEnd();
  } else {
    if (child_) {
      child_->WriteFinish();
      delete child_;
      child_=NULL;
    }
    return WriteFinish();
  }
}

size_t RecordItem::Write(const zint32 label, const void *data, size_t size, size_t count) {
  ZCHECK_FALSE(CheckLabelExist(label)) 
    << "Label "<<label<<" already existed!\n";
  table_.push_back(make_pair(label, GetPos()));
  return Write(data, size, count);
}

size_t RecordItem::Write(const void *data, size_t size, size_t count) {
  return fwrite(data, size, count, fp_);
}

bool RecordItem::LabelExist(const zint32 label) {
  if (child_!=NULL)
    return child_->LabelExist(label);
  return CheckLabelExist(label);
}
bool RecordItem::CheckLabelExist(const zint32 label) {
  for (zuint i=0; i<table_.size(); i++) 
    if (table_[i].first==label) return true;
  return false;
}
zint64 RecordItem::GetChildPos(const zint32 label) {
  for (zuint i=0; i<table_.size(); i++)
    if (table_[i].first==label) return table_[i].second;
  return -1;
}
zint64 RecordItem::GetPos() {
  fpos_t pos;
  ZCHECK(fgetpos(fp_, &pos)==0);
  return pos;
}
void RecordItem::SetPos(zint64 pos) {
  fpos_t fpos=pos;
  ZCHECK(fsetpos(fp_, &fpos)==0);
}

size_t RecordItem::GetItemNumber()
{
  return table_.size();
}

//////////////////////////////////////////////////////////////////////////
// RecordFile
const int RECORDFILE_MAGIC=1124;

RecordFile::RecordFile()
  :head_(NULL), fp_(NULL), cur_(NULL) {}
void RecordFile::LoadFileBegin(const string &filename) {
  ZCHECK_NULL(fp_);
  ZCHECK_NULL(head_);

  ZCHECK(fp_=fopen(filename.c_str(), "rb"));
  int magic;
  fread(&magic, sizeof(magic), 1, fp_);
  ZCHECK(magic==RECORDFILE_MAGIC);
  head_=new RecordItem(fp_, sizeof(RECORDFILE_MAGIC), NULL);
  head_->ReadTable();
  cur_=head_;
  write_=false;
}
void RecordFile::LoadFileEnd() {
  ZCHECK_NOT_NULL(fp_);
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  head_->ReadFinish();
  delete head_;
  head_=NULL;
  cur_=NULL;
  ZCHECK(fclose(fp_)==0);
  fp_=NULL;
}

void RecordFile::SaveFileBegin(const string &filename) {
  ZCHECK_NULL(fp_);
  ZCHECK_NULL(head_);

  ZCHECK(fp_=fopen(filename.c_str(), "wb")) << ZVAR(filename);
  fwrite(&RECORDFILE_MAGIC, sizeof(RECORDFILE_MAGIC), 1, fp_);
  head_=new RecordItem(fp_, sizeof(RECORDFILE_MAGIC), NULL);
  cur_=head_;
  write_=true;
}

void RecordFile::SaveFileEnd() {
  ZCHECK_NOT_NULL(fp_);
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  head_->WriteFinish();
  delete head_;
  head_=NULL;
  cur_=NULL;
  ZCHECK_ZERO(fclose(fp_));
  fp_=NULL;
}

void RecordFile::ReadChildBegin(const zint32 label) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  cur_=cur_->ReadChild(label);
}

void RecordFile::ReadChildEnd() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  cur_=cur_->ReadFinish();
}

void RecordFile::ReadRepeatBegin(const zint32 label) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  cur_=cur_->ReadRepeatBegin(label);
}

bool RecordFile::ReadRepeatChild() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  RecordItem *next=cur_->ReadRepeatChild();
  if (next==NULL) return false;
  cur_=next;
  return true;
}

void RecordFile::ReadRepeatEnd() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  cur_=cur_->ReadRepeatEnd();
}

size_t RecordFile::Read(void *data, size_t size, size_t count) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  return cur_->Read(data, size, count);
}

size_t RecordFile::Read(const zint32 label, void *data, size_t size, size_t count) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK_FALSE(write_);
  return cur_->Read(label, data, size, count);
}

void RecordFile::WriteChildBegin(const zint32 label) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  cur_=cur_->WriteChild(label);
}

void RecordFile::WriteChildEnd() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  cur_=cur_->WriteFinish();
}

void RecordFile::WriteRepeatBegin(const zint32 label) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  cur_=cur_->WriteRepeatBegin(label);
}

void RecordFile::WriteRepeatChild() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  cur_=cur_->WriteRepeatChild();
}

void RecordFile::WriteRepeatEnd() {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  cur_=cur_->WriteRepeatEnd();
}

size_t RecordFile::Write(const void *data, size_t size, size_t count) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  return cur_->Write(data, size, count);
}
size_t RecordFile::Write(const zint32 label, const void *data, size_t size, size_t count) {
  ZCHECK_NOT_NULL(head_);
  ZCHECK(write_);
  return cur_->Write(label, data, size, count);
}


bool RecordFile::LabelExist(const zint32 label) {
  return cur_->LabelExist(label);
}
};  // namespace zzz